package org.ediba.siri.core.common.data;

import static org.hibernate.criterion.Example.create;

import java.io.Serializable;
import java.util.Collection;
import java.util.List;
import java.util.Map;

import org.ediba.siri.core.Filter;
import org.hibernate.LockMode;
import org.hibernate.criterion.Conjunction;
import org.hibernate.criterion.DetachedCriteria;
import org.hibernate.criterion.Disjunction;
import org.hibernate.criterion.Example;
import org.hibernate.criterion.MatchMode;
import org.hibernate.criterion.Order;
import org.hibernate.criterion.Projections;
import org.hibernate.criterion.Restrictions;
import org.hibernate.exception.ConstraintViolationException;
import org.springframework.orm.hibernate3.support.HibernateDaoSupport;

public class HibernateDomainDao<T, K extends Serializable>
			extends HibernateDaoSupport
			implements DomainDao<T, K>{

	protected Class<T> type;
	protected HibernateDomainDao(Class<T> type){this.type = type;}
	
	@Override
	public void persist(T transientInstance) {
		this.getHibernateTemplate().persist(transientInstance);
	}

	@Override
	public void attachDirty(T instance) {
		this.getHibernateTemplate().saveOrUpdate(instance);//this.getHibernateTemplate().save(instance);
	}

	@Override
	public void attachClean(T instance) {
		this.getHibernateTemplate().lock(instance, LockMode.NONE);
	}

	@Override
	public void delete(T persistentInstance) {
		this.getHibernateTemplate().delete(persistentInstance);
	}
	
	@Override
	public boolean delete(int id) {
		boolean _success = false;
		T persistentInstance = this.getHibernateTemplate().get(this.type , id);
		try{
			if (persistentInstance != null){
				this.getHibernateTemplate().delete(persistentInstance);
				_success = true;
			}
			else
				_success = false;
		}catch (ConstraintViolationException e) {
			_success = false;
		}
		return _success;
	}

	@Override
	public T merge(T detachedInstance) {
		return this.getHibernateTemplate().merge(detachedInstance);
	}

	@Override
	public T findById(K id) {
		return this.getHibernateTemplate().get(this.type , id);
	}
	
	/********************* massive  *********************/

	@SuppressWarnings("unchecked")
	@Override
	public List<T> findByExample(T instance, String sort, boolean dir) {
		DetachedCriteria _detachedCriteria = this.buildCriteria(instance);
		if (sort != null)
			_detachedCriteria.addOrder(this.buildOrder(sort,dir));
		return this.getHibernateTemplate().findByCriteria(_detachedCriteria);
	}

	@SuppressWarnings("unchecked")
	@Override
	public List<T> findByExample(T instance, String sort, boolean dir, Integer start, Integer limit) {
		DetachedCriteria _detachedCriteria = this.buildCriteria(instance);
		if (sort != null)
			_detachedCriteria.addOrder(this.buildOrder(sort,dir));
		return this.getHibernateTemplate().findByCriteria(_detachedCriteria, start != null ? start : DEFAULT_START, limit != null ? limit : DEFAULT_LIMIT);
	}

	@SuppressWarnings("unchecked")
	@Override
	public List<T> findByFilters(Collection<Map<Filter,Object>> filters, String sort, boolean dir){
		DetachedCriteria _detachedCriteria;
		if (filters != null) 
			_detachedCriteria = this.buildCriteria(filters); 
		else 
			_detachedCriteria = this.buildCriteria();
		if (sort != null) 
			_detachedCriteria.addOrder(this.buildOrder(sort,dir));
		return this.getHibernateTemplate().findByCriteria(_detachedCriteria);
	}

	@SuppressWarnings("unchecked")
	@Override
	public List<T> findByFilters(Collection<Map<Filter, Object>> filters, String sort, boolean dir, Integer start, Integer limit) {
		DetachedCriteria _detachedCriteria;
		if (filters != null) 
			_detachedCriteria = this.buildCriteria(filters); 
		else 
			_detachedCriteria = this.buildCriteria();
		if (sort != null)
			_detachedCriteria.addOrder(this.buildOrder(sort,dir));
		return this.getHibernateTemplate().findByCriteria(_detachedCriteria, start != null ? start : DEFAULT_START, limit != null ? limit : DEFAULT_LIMIT);
	}
	
	@SuppressWarnings("unchecked")
	@Override
	public List<T> findByFilters(Map<Filter,Collection<Object>> filters, String sort, boolean dir){
		DetachedCriteria _detachedCriteria;
		if (filters != null) 
			_detachedCriteria = this.buildCriteria(filters); 
		else 
			_detachedCriteria = this.buildCriteria();
		if (sort != null) 
			_detachedCriteria.addOrder(this.buildOrder(sort,dir));
		return this.getHibernateTemplate().findByCriteria(_detachedCriteria);
	}

	@SuppressWarnings("unchecked")
	@Override
	public List<T> findByFilters(Map<Filter, Collection<Object>> filters, String sort, boolean dir, Integer start, Integer limit) {
		DetachedCriteria _detachedCriteria;
		if (filters != null) 
			_detachedCriteria = this.buildCriteria(filters); 
		else 
			_detachedCriteria = this.buildCriteria();
		if (sort != null)
			_detachedCriteria.addOrder(this.buildOrder(sort,dir));
		return this.getHibernateTemplate().findByCriteria(_detachedCriteria, start != null ? start : DEFAULT_START, limit != null ? limit : DEFAULT_LIMIT);
	}	
	
	@SuppressWarnings("unchecked")
	@Override
	public List<T> getAll(String sort, boolean dir) {
		DetachedCriteria dc = this.buildCriteria();
		if (sort != null) dc.addOrder(this.buildOrder(sort,dir));
		return this.getHibernateTemplate().findByCriteria(dc);
	}

	@SuppressWarnings("unchecked")
	@Override
	public List<T> getAll(String sort, boolean dir, Integer start, Integer limit) {
		DetachedCriteria dc = this.buildCriteria();
		if (sort != null) dc.addOrder(this.buildOrder(sort,dir));
		return this.getHibernateTemplate().findByCriteria(dc, start != null ? start : DEFAULT_START, limit != null ? limit : DEFAULT_LIMIT);
	}
	
	/********************* criteria builders  *********************/
	
	protected DetachedCriteria buildCriteria(){
		return DetachedCriteria.forClass(this.type);
	}
	
	protected DetachedCriteria buildCriteria(T example){
		DetachedCriteria detachedCriteria = this.buildCriteria();
		Example example1 = create(example);
		example1.excludeZeroes();
		return detachedCriteria.add(create(example));
	}
	
	protected DetachedCriteria buildCriteria(Collection<Map<Filter,Object>> filters){
		DetachedCriteria detachedCriteria = this.buildCriteria();
		Disjunction or = Restrictions.disjunction();
		Conjunction and;
		
		for (Map<Filter, Object> filter : filters) {
			and = Restrictions.conjunction();
			for (Map.Entry<Filter,Object> entry: filter.entrySet())
				switch (entry.getKey()) {
					case ID:	and.add(Restrictions.idEq(entry.getValue()));
					break;
					default:	and.add(Restrictions.ilike(entry.getKey().getProperty(),(String)entry.getValue(),MatchMode.ANYWHERE));	
					break;
				}
			or.add(and);
		}
		detachedCriteria.add(or);
		
		return detachedCriteria;
	}
	
	protected DetachedCriteria buildCriteria(Map<Filter,Collection<Object>> filters){
		DetachedCriteria detachedCriteria = this.buildCriteria();
		Disjunction or;
		Conjunction and = Restrictions.conjunction();
		
		for (Map.Entry<Filter,Collection<Object>> filter : filters.entrySet()){
			or = Restrictions.disjunction();			
			for (Object value : filter.getValue()) 
				switch (filter.getKey()) {
				case ID:	or.add(Restrictions.idEq(value));
				break;
				default:	or.add(Restrictions.ilike(filter.getKey().getProperty(),(String)value,MatchMode.ANYWHERE));	
				break;
				}
			and.add(or);
		}
			
		detachedCriteria.add(and);
		
		return detachedCriteria;
	}
	
	protected Order buildOrder(String sort, boolean dir){
		Order order;
		if (dir)
			order = Order.asc(sort);
		else 
			order = Order.desc(sort);
		return order;
	}

	@Override
	public int findByExampleCount(T instance) {
		DetachedCriteria dc = this.buildCriteria(instance);
		dc.setProjection(Projections.countDistinct(Filter.ID.getProperty()));
		return (Integer)this.getHibernateTemplate().findByCriteria(dc).get(0);
	}
	
	@Override
	public int findByFiltersCount(Map<Filter, Collection<Object>> filters) {
		DetachedCriteria dc = this.buildCriteria(filters);
		dc.setProjection(Projections.countDistinct(Filter.ID.getProperty()));
		return (Integer)this.getHibernateTemplate().findByCriteria(dc).get(0);
	}

	@Override
	public int findByFiltersCount(Collection<Map<Filter, Object>> filters) {
		DetachedCriteria dc = this.buildCriteria(filters);
		dc.setProjection(Projections.countDistinct(Filter.ID.getProperty()));
		return (Integer)this.getHibernateTemplate().findByCriteria(dc).get(0);
	}
	
	@Override
	public int getAllCount() {
		DetachedCriteria dc = this.buildCriteria();
		dc.setProjection(Projections.countDistinct(Filter.ID.getProperty()));
		return (Integer)this.getHibernateTemplate().findByCriteria(dc).get(0);
	}

}
